#ifdef WIN32
#include <windows.h>
#endif

#include <GL/gl.h>
#include <GL/glu.h>
#include <string>
#include <iostream>

#include "GLRenderer.h"

#include "iRenderTarget.h"

#include "neiderra/core/ResourceManager.h"
// #include "UtilsASELoader.h"
// #include "UtilsBMPReader.h"
// #include "Utils3dsLoader.h"

#include "neiderra/core/io/IOStream.h"

#include "Object.h"
#include "Mesh.h"

#include "TextString.h"

//#include "logger.h"

#include "neiderra/core/Core.h"
#include "neiderra/math/Vectors.h"

// #include "SceneCamera.h"
#include "neiderra/math/Quaternion.h"

//#include "core/SceneManager.h"
// #include "SceneObject.h"
//#include "SceneObjectFPSCounter.h"
#include "SGNodes.h"

#include "Texture.h"
#include "Alphabet.h"
#include "ParticleSystem.h"
#include "Font.h"
#include "neiderra/math/Misc.h"
#include "neiderra/gui/Window.h"

//#include "Scene.h"
//using neiderra::scene::Scene;

//#include <ft2build.h>
//#include <freetype/ftglyph.h>
//#include FT_FREETYPE_H

using namespace neiderra::core;
using namespace neiderra::gui;
using namespace neiderra::rendering;
// using namespace neiderra::core::io::loaders;
// using namespace neiderra::core::io;
// using neiderra::scene::aCamera;
// using neiderra::scene::aSceneObject;
// using namespace neiderra::types;
// using namespace neiderra::math;
// using neiderra::core::gui::aWindow;
// using neiderra::core::gui::aWindowImpl;
// //using neiderra::scene::soFPSCounter;
// using std::cout;
// using std::endl;

//unsigned int texture_name;
//void drawAxes();
//bool texrender = false;

glRenderer::glRenderer(Core* core): iRenderer(core) {
// 	core->getLogger()->traceBlock("glRenderer::glRenderer()");
	target_ = 0;

// 	Logger &log = *core->getLogger();
	_timeElapsed = 0;
	_fps = 0;
	_frameCount=0;
	_root = 0;

	_showAxes = false;
	
/*	FT_Error err = FT_Init_FreeType ( &_lib );
	if(err) {
		core->getLogger()->log( "Can't init freetype" );
	}
	
	core->getLogger()->log( "Freetype initialized successfully"  );
	
	err = FT_New_Face( _lib, "data/fonts/Arial.ttf", 0, &_face);
	
	if ( err == FT_Err_Unknown_File_Format )
	{
		//... the font file could be opened and read, but it appears
				//... that its font format is unsupported
		core->getLogger()->log("the fonts file could not be opened and read, but it appears that its font format is unsupported");
	}
	else if ( err )
	{
		core->getLogger()->log("... another error code means that the font file could not be opened or read, or simply that it is broken...");
	}
	core->getLogger()->log("font face loaded successfully");
	
	err = FT_Set_Char_Size(
			_face,    
				0,       // char_width in 1/64th of points  
				16*64,   // char_height in 1/64th of points 
				300,    // horizontal device resolution    
				300 );   // vertical device resolution      
	
	if (err) core->getLogger()->log( "Setting font size error");*/
	
//	log << "testing log" << "next line" << "is it ok?";

	//gluLookAt(0,0,0, 0,0,0, 0,1,0);

//	core->getLogger()->log("Loading mesh...");
//	mesh_ = core->getResourceManager()->getASELoader()->loadASEFile("data\\models\\cube.ase");
/*	iInputStream *input = new win32InputStream(core);
	if(input->open("data\\models\\sample.3ds")){
		mesh_ = core->getResourceManager()->get3dsLoader()->load3dsFile(input);
		input->close();
	}
	//else { core->getLogger()->
	delete input;*/

/*	iInputStream* bmpfile = new win32InputStream(core);
	if(bmpfile->open("data\\image.bmp")){
		aBMPReader* bmpr = core->getResourceManager()->getBMPReader();
		img_ = bmpr->loadFromStream(bmpfile);
		bmpfile->close();
	}
	delete bmpfile;*/

// 	core->getLogger()->leaveBlock();
}

glRenderer::~glRenderer(){
	//delete mesh_;
	//delete img_;
	/*FT_Error err = FT_Done_FreeType ( _lib );
	if(err)
	{
		core->getLogger()->log( "Can't destroy freetype" );
	}
	
	core->getLogger()->log( "FreeType lib handle destroyed" );*/
}

void glRenderer::init(iRenderTarget* impl)
{
	target_ = impl;
	//glEnableClientState(GL_NORMAL_ARRAY);

	glEnable(GL_DEPTH_TEST);


//	_textures[0] = 0;
}

void glRenderer::render(Image* img, unsigned x, unsigned y)
{
	setOrthoMode();
	//glMatrixMode(GL_PROJECTION);
	//glPushMatrix();
	//glLoadIdentity();

	//gluOrtho2D (0.0, (GLdouble) target_->getWidth(), 0.0, (GLdouble) target_->getHeight());
	//glMatrixMode(GL_MODELVIEW);
	//glLoadIdentity();

	unsigned w = img->getWidth();
	unsigned h = img->getHeight();
	y = target_->getHeight()-h-y;

	glRasterPos2i(x, y);
	glDrawPixels(w, h, GL_RGB, GL_UNSIGNED_BYTE, img->getData());

	//glPopMatrix();
	setPerspectiveMode();
}

// void glRenderer::render(Scene* scene)
// {
// 	if(!scene) 
// 		return;
// }

// void glRenderer::render(aSceneObject* obj)
// {
// 	if(obj)
// 		obj->render(this);
// }

void glRenderer::setOrthoMode()
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D (0.0, (GLdouble) target_->getWidth(), 0.0, (GLdouble) target_->getHeight());
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
}

void glRenderer::setPerspectiveMode()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void glRenderer::renderTexture(Texture* texture)
{
	if(!texture) return;
	if(!texture->texture) return;

	glGenTextures(1, &texture->name);
	glBindTexture(GL_TEXTURE_2D, texture->name);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 
		texture->texture->getWidth(), 
		texture->texture->getHeight(), 
		0, GL_RGB, GL_UNSIGNED_BYTE, 
		texture->texture->getData());

}

void glRenderer::render(Mesh* mesh, Texture* t)
{
	Texture* texture = mesh->getTexture();
	if(t)
		texture = t;
//	setPerspectiveMode();

	//glMatrixMode(GL_MODELVIEW);
	const int w = this->target_->getWidth();
	const int h = this->target_->getHeight();

	const int vn = mesh->getVertexNum();
	vertex *vertices = mesh->getVertices();

	const int fn = mesh->getFaceNum();
	face *faces = mesh->getFaces();

	const int mn = mesh->getMapsNum();
	mapstruct* maplist = mesh->getMapList();

	const int nn = mesh->getNormalsNum();
	vec3* normals = mesh->getNormals();

	vec3 maxDims;
	mesh->getMaxDims(maxDims);

	glEnableClientState(GL_VERTEX_ARRAY);
	
	if(mn&&texture)	
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glEnable(GL_TEXTURE_2D);
		glTexCoordPointer(2, GL_FLOAT, 0, maplist);
		glBindTexture(GL_TEXTURE_2D, texture->name);
	}

	glVertexPointer(3, GL_FLOAT, 0, vertices);
	
	if(mn&&texture)
		glDrawElements(GL_TRIANGLES, fn*3, GL_UNSIGNED_INT, faces);
	else
        glDrawElements(GL_LINE_STRIP, fn*3, GL_UNSIGNED_INT, faces);

//	glDrawArrays(GL_TRIANGLES, 0, fn*3);

	if(mn)
	{
		glDisable(GL_TEXTURE_2D);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	glDisableClientState(GL_VERTEX_ARRAY);
	
// // old method
//	int i=0, j=0;
//	face* f;
//	vertex *vx0, *vx1, *vx2;
//	while(i<fn){
//		j=i*3;
//		int v0, v1, v2;
//		f = faces+i;
//		v0 = f->v1;
//		v1 = f->v2;
//		v2 = f->v3;
//
//		vx0 = vertices+v0;
//		vx1 = vertices+v1;
//		vx2 = vertices+v2;
//
//		if(texture){
//			glEnable(GL_TEXTURE_2D);
//			glBindTexture(GL_TEXTURE_2D, texture->name);
//		}
//		glBegin(GL_TRIANGLES);
////			glNormal3f(normals[j].x, normals[j].y, normals[j].z);
//			glTexCoord2f(maplist[v0].u, maplist[v0].v);
//			glVertex3f(vx0->x, vx0->y, vx0->z);
//			
////			glNormal3f(normals[j+1].x, normals[j+1].y, normals[j+1].z);
//			glTexCoord2f(maplist[v1].u, maplist[v1].v);
//			glVertex3f(vx1->x, vx1->y, vx1->z);
//			
////			glNormal3f(normals[j+2].x, normals[j+2].y, normals[j+2].z);
//			glTexCoord2f(maplist[v2].u, maplist[v2].v);
//			glVertex3f(vx2->x, vx2->y, vx2->z);
//		glEnd();
//		if(texture) {
//			glDisable(GL_TEXTURE_2D);
//		}
//
//		i++;
//	}
//	drawAxes();
}

void glRenderer::rotatef(float angle, float x, float y, float z)
{
	glMatrixMode(GL_MODELVIEW);
	glRotatef(angle,x,y,z);
}

void glRenderer::rotatef(float angle, vec3& vec)
{
	glMatrixMode(GL_MODELVIEW);
	glRotatef(angle,vec.x,vec.y,vec.z);
}

void glRenderer::translatef(float x, float y, float z)
{
	glMatrixMode(GL_MODELVIEW);
	glTranslatef(x,y,z);
}

void glRenderer::translatef(vec3& vec)
{
	glMatrixMode(GL_MODELVIEW);
	glTranslatef(vec.x,vec.y,vec.z);
}

void glRenderer::scalef(float x, float y, float z)
{
    glMatrixMode(GL_MODELVIEW);
	glScalef(x,y,z);
}

void glRenderer::scalef(vec3& vec)
{
    glMatrixMode(GL_MODELVIEW);
	glScalef(vec.x, vec.y, vec.z);
}

void glRenderer::identity()
{
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void glRenderer::pushMatrix()
{
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
}

void glRenderer::popMatrix()
{
    glMatrixMode(GL_MODELVIEW);	
	glPopMatrix();
}

void glRenderer::multMatrix(float *m)
{
    glMultMatrixf((GLfloat*)m);
}

void glRenderer::RenderRoot()
{
	if(_root)
		_root->traverse(this);
}

void glRenderer::update(double deltaT)
{
	// fixme 1: render target
	if(target_ == 0)
		return;
	
	_timeElapsed += deltaT;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	const unsigned w = target_->getWidth(),	h = target_->getHeight();

	glViewport(0,0,w,h);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	//if(w<=h) glFrustum(-2.0, 2.0, -2.0*(GLfloat)h/(GLfloat)w, 2.0*(GLfloat)h/(GLfloat)w, 2.0, 20.0);
	//else glFrustum(-2.0, 2.0, -2.0*(GLfloat)w/(GLfloat)h, 2.0*(GLfloat)w/(GLfloat)h, 2.0, 20.0);
	gluPerspective(45.0f, (GLfloat)w/ (GLfloat)h, 0.1f, 100.0f);
 	//gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 1.0, 10000);
	glTranslatef(-1.5f,0.0f,-6.0f);	
// 	gluLookAt(0,0,-1,0,0,1,0,1,0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// camera SG node
// 	aCamera* camera = core->getSceneManager()->GetDefaultScene()->GetCamera();
// 	
// 	vec3 view, up, pos;
// 	camera->GetDestination(view);
// 	camera->GetPosition(pos);
// 	camera->GetUpVector(up);
// 
// 	gluLookAt(pos.x, pos.y, pos.z, 
// 		pos.x + view.x, pos.y + view.y, 
// 		pos.z + view.z, up.x, up.y, up.z);

	sgNode::currentPass = 1;
	RenderRoot();

	sgNode::currentPass = 2;
	RenderRoot();
	
	glTranslatef(-1.5f,0.0f,-6.0f);	
	
	glBegin(GL_TRIANGLES);
		glVertex3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-1.0f, -1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 0.0f);
	glEnd();
	
	if(_showAxes)
		drawAxes();
    
	if(target_){
		glFlush();
		target_->swapBuffers();
		_frameCount++;

		if(_timeElapsed >= 1000)
		{
			_fps = _frameCount * _timeElapsed / 1000;
			_timeElapsed = 0;
			_frameCount = 0;
		}
	}
}

void glRenderer::render(sg::sgParticleSystemNode* node)
{

}

float glRenderer::getFPS()
{
    return _fps;
}

void glRenderer::render(Alphabet* alphabet)
{
/*    if(!alphabet) return;
	
	Image* img=0;
	Texture* texture=0;
	FT_Library library;
	FT_Error error = FT_Init_FreeType( &library );
	if(error)
	{
// 		core->getLogger()->log("can't initialize freetype");
		return;
	}
	
	FT_Face face;
	error = FT_New_Face( library, alphabet->getFont()->path.c_str(), 0, &face);

	if( error == FT_Err_Unknown_File_Format )
	{
// 		core->getLogger()->log("unknown file format");
		FT_Done_FreeType( library );
		return ;
	}
	error = FT_Set_Char_Size( face, 0, alphabet->getFont()->fontSize * 64, 75, 75 );

	FT_UInt glyph_index;
	FT_ULong  charcode;

	charcode = FT_Get_First_Char( face, &glyph_index );

	GLuint base = glGenLists(128);
	alphabet->setLetterBase((unsigned)base);

	do
	{
		// load glyph image into the slot (erase previous one) 
		error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );

		// convert to an anti-aliased bitmap 
		error = FT_Render_Glyph( face->glyph, ft_render_mode_normal );

		FT_GlyphSlot slot = face->glyph;
//		FT_Bitmap& bitmap = &slot->bitmap;
		FT_BBox bbox;
		FT_Glyph glyph;
		
		if (FT_Get_Glyph( slot, &glyph )){  }

		FT_Glyph_Get_CBox( glyph, FT_LOAD_NO_SCALE, &bbox );

		FT_Glyph_To_Bitmap( &glyph, ft_render_mode_normal, 0, 1 );
		FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph) glyph;
		FT_Bitmap& bitmap = bitmap_glyph->bitmap;
		

		//unsigned letterW = (bbox.xMax - bbox.xMin)/64;
		//unsigned letterH = (bbox.yMax - bbox.yMin)/64;
		unsigned letterW = bitmap.width;
		unsigned letterH = bitmap.rows;


		if(!letterW || !letterH) {
			//img = new Image(0, 0);
			//alphabet->setLetter(charcode, img);
			//texture = new Texture();
			//texture->texture = img;
			charcode = FT_Get_Next_Char( face, charcode, &glyph_index );
			continue;
		}

		// OpenGL 1.1 supports only square textures
		unsigned letterWidth = math::next_p2(letterW);
		unsigned letterHeight = math::next_p2(letterH);
		
		nByte* buf = new nByte[4*letterWidth*letterHeight];

		for(int j=0,k=letterH-1; j<letterH; j++,k--)
		{
			const int klw = k*letterW;
			for(int i=0, p=0; i<letterW*4; i+=4, p++)
			{
				nByte value = bitmap.buffer[klw + p];
				const int pos = letterWidth*4*j+i;
                buf[pos+0] = value;
				buf[pos+1] = value;
				buf[pos+2] = value;
				buf[pos+3] = value;
			}
		}
        
//		img = new Image(letterWidth, letterHeight, buf);
//		alphabet->setLetter(charcode, img);
		//texture = new Texture();
		//texture->texture = img;
		GLuint textureName;

//		this->renderTexture(texture);	// :-)
		
		glGenTextures(1, &textureName);
		glBindTexture(GL_TEXTURE_2D, textureName);

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

//		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, letterWidth, 
			letterHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
		
		delete[] buf;

		glNewList(base+charcode, GL_COMPILE);
			
			glBindTexture(GL_TEXTURE_2D, textureName);
			
			glPushMatrix();

			glTranslatef(0, bitmap_glyph->top - bitmap.rows, 0);
			float x = (float) bitmap.width / (float) letterWidth;
			float y = (float) bitmap.rows / (float) letterHeight;

			glBegin(GL_QUADS);
				glTexCoord2d(0,0); glVertex2f(0,0);
				glTexCoord2d(0,y); glVertex2f(0,letterH);
				glTexCoord2d(x,y); glVertex2f(letterW, letterH);
				glTexCoord2d(x,0); glVertex2f(letterW, 0);
			glEnd();

			glPopMatrix();
			glTranslatef(face->glyph->advance.x >> 6, 0, 0);

		glEndList();

	    charcode = FT_Get_Next_Char( face, charcode, &glyph_index );
	}
	while ( glyph_index != 0 );

	FT_Done_Face( face );
	FT_Done_FreeType( library );*/
}

void glRenderer::render(TextString* textString, unsigned pen_x, unsigned pen_y)
{
 //   setOrhtoMode();

	//glTranslatef(pen_x, pen_y, 0);

	//for(int i=0; i< textString->GetLength(); i++)
	//{
	//	glCa
	//}

	//setPerspectiveMode();
}

void glRenderer::render(sg::sgNode*)
{
	// do nothing
}

void glRenderer::render(sg::sgDOFTranslateNode* node)
{
	glTranslatef(node->_trans.x, node->_trans.y, node->_trans.z);
}

void glRenderer::render(sg::sgDOFRotateNode* node)
{
	float mat[16];
	math::convertQuatToMatrix(node->_rotation, mat);
	glMultMatrixf(mat);

	if(_showAxes)
		drawAxes();
}

void glRenderer::render(sg::sgDOFScaleNode* node)
{
}

iRenderTarget* glRenderer::getTarget()
{
	return target_;
}

void glRenderer::drawAxes()
{
	glPushAttrib(GL_CURRENT_BIT);
	glBegin(GL_LINES);
		glColor3f(2, 0, 0);
		glVertex3d(0, 0, 0);
		glVertex3d(2, 0, 0);
		glColor3f(0, 2, 0);
		glVertex3d(0, 0, 0);
		glVertex3d(0, 2, 0);
		glColor3f(0, 0, 2);
		glVertex3d(0, 0, 0);
		glVertex3d(0, 0, 2);
	glEnd();
	glPopAttrib();
}

void glRenderer::render(sg::sgStringNode* node)
{
	setOrthoMode();
	
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glTranslatef(node->_x, node->_y, 0);
	
	string& s = node->_s;
	const size_t len = s.length();
	GLuint base = node->_alphabet->getLetterBase();

	glListBase(base);
	for(int i=0; i<len; i++)
	{
		glCallList(base+s[i]);
	}

	glDisable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);

	setPerspectiveMode();
}

void glRenderer::render(RenderingObject* obj)
{}

void glRenderer::render(sgDOFNode* node)
{
	render(node->v);
}

void glRenderer::render(vec3& v)
{
	v.normalize();
	glPushAttrib(GL_CURRENT_BIT);
    glBegin(GL_LINES);
		glColor3f(1,1,1);
		glVertex3f(0,0,0);
		glVertex3f(v.x,v.y,v.z);
	glEnd();
    glPopAttrib();
}

void glRenderer::render(ParticleSystem* particleSystem)
{
//	Particles* particles = particleSystem->getParticles();
//	Particles::iterator i = particles->begin();
//
//	for(;i!=particles->end();i++)
//	{
//        Particle* p = *i;
////		p->render(this);
//	}
}
